package ar.unc.famaf.pln.giveselector.context;

import java.io.Serializable;
import java.util.*;

import ar.unc.famaf.pln.giveselector.selectors.ACLSelector;

import give.comm.ActionMessage;
import give.formula.*;

/**
 * A Context represents a set of features describing the situation. It is both
 * used to represent the context when an utterance is produced and the context
 * when an utterance must be produced.
 * @author Alexandre Denis
 * @author Luciana Benotti
 */
@SuppressWarnings("serial")
public class Context implements Serializable
{
	private String region; 				// the current region of the player
	private Orientation orientation; 	// the current orientation of the player

	// both lists are sorted by chronological order
	private List<Atom> pastActions; 	// the actions that already have been executed
	private List<Atom> plannedActions; 	// the actions that the planner returns with the current state and the goal


	/**
	 * Creates a Context with only the region.
	 */
	public Context(String region)
	{
		this.region = region;
		this.pastActions = new ArrayList<Atom>();
		this.plannedActions = new ArrayList<Atom>();
	}


	/**
	 * Creates a copy Context with given Context.
	 */
	public Context(Context context)
	{
		this.region = context.getRegion();
		this.orientation = context.getOrientation();
		this.pastActions = new ArrayList<Atom>(context.getPastActions());
		this.plannedActions = new ArrayList<Atom>(context.getFutureActions());
	}


	/**
	 * Updates the Context by setting the region. If the given region is
	 * different from the current region, adds a new past move action.
	 * @return true if a move action has been added, false otherwise
	 */
	public boolean updateMove(String region)
	{
		if (!region.equals(this.region))
		{
			pastActions.add(new Atom("move", new Constant(this.region), new Constant(region)));
			System.out.println("DF: "+new Atom("move", new Constant(this.region), new Constant(region)));
			this.region = region;
			return true;
		}
		else return false;
	}


	/**
	 * @return the orientation
	 */
	public Orientation getOrientation()
	{
		return orientation;
	}


	/**
	 * @param orientation the orientation to set
	 */
	public void setOrientation(Orientation orientation)
	{
		this.orientation = orientation;
	}


	/**
	 * Adds a past action from an ActionMessage. This method always assume the
	 * action is a 'manipulate' action whose first argument is the manipulated
	 * button.
	 */
	public void addPastAction(ActionMessage actionMsg)
	{
		Atom header = actionMsg.getHeader();
		pastActions.add(new Atom(header.getPredicate(), header.getArguments().get(0)));
	}


	/**
	 * Returns a String representation of these Context.
	 */
	public String toString()
	{
		return pastActions + " " + orientation + " " + plannedActions;
	}


	/**
	 * Returns the immediate past action.
	 * @return null if there is none
	 */
	public Atom getImmediatePastAction()
	{
		if (pastActions.isEmpty())
			return null;
		else return pastActions.get(pastActions.size() - 1);
	}


	/**
	 * Returns the immediate future action.
	 * @return null if there is none
	 */
	public Atom getImmediateFutureAction()
	{
		if (plannedActions.isEmpty())
			return null;
		else return plannedActions.get(0);
	}
	
	/**
	 * Returns n future actions 
	 * (or if there are less than n planned actions left, return the whole plan).
	 * @return null if there is none
	 */
	public List<Atom> getNFutureAction(int n)
	{
		if (plannedActions.isEmpty() || plannedActions == null)
			return null;
		else if (plannedActions.size()<n) 
		{
			return plannedActions.subList(0, plannedActions.size());
		}
		else return plannedActions.subList(0, n);
	}



	/**
	 * Returns the past actions.
	 * @return
	 */
	public List<Atom> getPastActions()
	{
		return pastActions;
	}


	/**
	 * Returns the future actions.
	 * @return
	 */
	public List<Atom> getFutureActions()
	{
		return plannedActions;
	}

	/**
	 * Returns the future manipulates.
	 * @return
	 */
	public List<Atom> getFutureManipulates()
	{
		List<Atom> ret = new ArrayList<Atom>();
		for (Atom action:plannedActions)
		{
			if (action.getPredicate().contains("manipulate"))
			{
				ret.add(action);
			}
		}
		ret = ACLSelector.cleanPlannerActions(ret);
		return ret;
	}

	/**
	 * Returns the future moves until the next manipulate.
	 * @return
	 */
	public List<Atom> getFutureMoveUntilManipulate()
	{
		List<Atom> ret = new ArrayList<Atom>();
		if (!(plannedActions.isEmpty() || plannedActions == null))
		{
			Atom action = plannedActions.get(0);
			int i = 0;
			while (!(action.getPredicate().contains("manipulate") || (action.getPredicate().contains("take"))))
			{
				ret.add(action);
				i++;
				action = plannedActions.get(i);
			}
		}
//		ret = ACLSelector.cleanPlannerActions(ret);
		return ret;
	}
	
	/**
	 * Sets the future actions.
	 */
	public void setFutureActions(List<Atom> actions)
	{
		plannedActions = actions;
	}


	/**
	 * Adds a past action to this Context.
	 */
	public void addPastAction(Atom action)
	{
		pastActions.add(action);
	}


	/**
	 * @param region the region to set
	 */
	public void setRegion(String region)
	{
		this.region = region;
	}


	/**
	 * @return the region
	 */
	public String getRegion()
	{
		return region;
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((plannedActions == null) ? 0 : plannedActions.hashCode());
		result = prime * result + ((orientation == null) ? 0 : orientation.hashCode());
		result = prime * result + ((pastActions == null) ? 0 : pastActions.hashCode());
		result = prime * result + ((region == null) ? 0 : region.hashCode());
		return result;
	}


	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Context))
			return false;
		Context other = (Context) obj;
		if (plannedActions == null)
		{
			if (other.plannedActions != null)
				return false;
		}
		else if (!plannedActions.equals(other.plannedActions))
			return false;
		if (orientation == null)
		{
			if (other.orientation != null)
				return false;
		}
		else if (!orientation.equals(other.orientation))
			return false;
		if (pastActions == null)
		{
			if (other.pastActions != null)
				return false;
		}
		else if (!pastActions.equals(other.pastActions))
			return false;
		if (region == null)
		{
			if (other.region != null)
				return false;
		}
		else if (!region.equals(other.region))
			return false;
		return true;
	}
}
